package com.dean.practice.gateway.common;

import io.netty.util.internal.PlatformDependent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 堆外内存监控
 * <p>如何回收堆外内存?</p>
 * <ul>方式一：Unsafe#freeMemory</ul>
 * <ul>方式二：JVM回收堆外内存</ul>
 * <p>
 * 1.JVM执行Full GC时会将DirectByteBuffer进行回收，回收之后Cleaner就不存在引用关系
 * 2.再下一次发生GC时会将Cleaner对象放入ReferenceQueue中，同时将Cleaner从链表中移除
 * 3.最后调用unsafe#freeMemory清除堆外内存
 *
 * <p>是否配置maxDirectMemory?</p>
 * {@link PlatformDependent#DIRECT_MEMORY_LIMIT}
 * <ui>1.优先读取：-Dio.netty.maxDirectMemory</ui>
 * <ui>2.再读取：-XX:MaxDirectMemorySize=40m，PS: 如果不设置该参数，默认与Xmx相同</ui>
 *
 * {@link io.netty.buffer.PooledByteBufAllocator#DEFAULT_NUM_DIRECT_ARENA} 决定
 * <ui>
 *     1.未配置最大堆外内存时，默认allocate创建的是：PooledUnsafeDirectByteBuf -->  io.netty.buffer.PooledByteBuf#deallocate(),
 *     堆外内存取决于系统资源。 (不会回收，哪怕是FullGC)
 * </ui>
 * <ui>
 *     2.当自定义最大堆外内存参数"-XX:MaxDirectMemorySize=40m", 则在allocate时创建的是：UnpooledDirectByteBuf，堆外内存取决于设置的值。
 *     (UnpooledUnsafeNoCleanerDirectByteBuf#doNotFree=false, 自动清理释放资源)
 *     <p> -Xmx4000m -Xms4000m -Xmn1500m -XX:SurvivorRatio=8 -XX:ParallelGCThreads=4 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:MaxTenuringThreshold=15 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled</p>
 * </ui>
 * <ui></ui>
 *
 * @author Dean
 * @date 2022/11/9
 */
@Slf4j
@Component
public class DirectMemoryReporter {

    private static final int _1K = 1024;

    private static final String BUSINESS_KEY = "TRACE_netty_direct_memory";

    private AtomicLong directMemory;

    @PostConstruct
    public void init() {
        Field field = ReflectionUtils.findField(PlatformDependent.class, "DIRECT_MEMORY_COUNTER");
        Objects.requireNonNull(field).setAccessible(true);
        try {
            directMemory = ((AtomicLong) field.get(PlatformDependent.class));
            startReport();
        } catch (IllegalAccessException e) {
            log.error("netty_direct_memory error", e);
        }
    }

    public void startReport() {
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        // 第二个参数为首次执行的延时时间，第三个参数为定时执行的间隔时间
        service.scheduleAtFixedRate(this::doReport, 30, 1, TimeUnit.SECONDS);
    }

    /**
     * 初始内存为16384k（16M），这是因为线上我们使用了池化堆外内存，默认一个 chunk 为16M
     */
    private void doReport() {
        try {
            int memoryInKB = (int) (directMemory.get());
            log.info("{}:{}KB", BUSINESS_KEY, memoryInKB / _1K);
        } catch (Exception e) {
            log.error("netty_direct_memory error", e);
        }
    }
}